Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created dynamic list of posts #916

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

davinnchii
Copy link

Copy link

@roman-mirzoian roman-mirzoian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job, but there is quite a bit of work to be done.

src/App.tsx Outdated
Comment on lines 34 to 38
onClick={() => {
if (isDropDownActive) {
setIsDropDownActive(false);
}
}}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This callback should be moved to a separate handler.

src/App.tsx Outdated
Something went wrong!
</div>
)}
{(selectedUser && !postsLoadingError) && (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better readability, conditions where there are more than two values should be assigned to separate variables.

Comment on lines 14 to 16
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [commentText, setCommentText] = useState('');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to have a managed state for one entity with simple inputs, it is better to keep everything in one state.

Comment on lines 26 to 37
createComment({
name,
email,
body: commentText,
postId: selectedPost.id,
}).then((newComment) => {
setComments(prev => [...prev, newComment]);
}).finally(() => {
setCommentText('');
setIsSubmitted(false);
setIsLoading(false);
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a catch block to catch possible errors and (optional) try to use try...catch construction (I think this design is more convenient for long records).

Comment on lines 57 to 70
{isLoading ? (
<Loader />
) : (
<>
{hasError ? (
<div className="notification is-danger" data-cy="CommentsError">
Something went wrong
</div>
) : (
<>
{
comments.length ? (
<>
<p className="title is-4">Comments:</p>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything works fine now, but it's written with a lot of nesting, so it's difficult to read and maintain in the future.
Try to avoid this by moving part of the code to the new Comments component.

<Loader />
) : (
<div data-cy="PostsList">
{posts.length > 0 ? (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{posts.length > 0 ? (
{posts.length ? (

Comment on lines 22 to 23
getUsers()
.then(setAllUsers);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a catch block.

>
{user.name}
</a>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Copy link

@Wesses Wesses left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link

@BudnikOleksii BudnikOleksii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great 🔥
Let's slightly improve your solution and fix some issues

src/App.tsx Outdated
const [isNewCommentActive, setIsNewCommentActive] = useState(false);
const [isDropDownActive, setIsDropDownActive] = useState(false);

const [postsLoadingError, setPostsLoadingError] = useState(false);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isPostsLoadingError or just isError

Comment on lines 17 to 40
<article
className="message is-small"
data-cy="Comment"
key={comment.id}
>
<div className="message-header">
<a href={`mailto:${comment.email}`} data-cy="CommentAuthor">
{comment.name}
</a>
<button
data-cy="CommentDelete"
type="button"
className="delete is-small"
aria-label="delete"
onClick={() => onDelete(comment.id)}
>
delete button
</button>
</div>

<div className="message-body" data-cy="CommentBody">
{comment.body}
</div>
</article>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a component Comment

Comment on lines 30 to 34
export const useComments = () => {
const comments = useContext(CommentsContext);

return comments;
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const useComments = () => {
const comments = useContext(CommentsContext);
return comments;
};
export const useComments = () => useContext(CommentsContext);

Comment on lines 31 to 36
const newComment = await createComment({
name,
email,
body: commentText,
postId: selectedPost.id,
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request to server must be inside try/catch block to catch server errors

Comment on lines 62 to 74
const handleChangeName = (event: React.ChangeEvent<HTMLInputElement>) => {
setCommentForm(prev => ({ ...prev, name: event.target.value }));
};

const handleChangeEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
setCommentForm(prev => ({ ...prev, email: event.target.value }));
};

const handleChangeComment = (
event: React.ChangeEvent<HTMLTextAreaElement>,
) => {
setCommentForm(prev => ({ ...prev, commentText: event.target.value }));
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can combine them into one handler. Input name you can get from event.target. Something like this

const handleChange = (
    event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
  ) => {
    const { name, value } = event.target;
    setCommentForm(prev => ({ ...prev, [name]: value }));
  };

Comment on lines 26 to 33
const toggleAddCommentForm = () => {
onAddComment(true);
};

<article className="message is-small" data-cy="Comment">
<div className="message-header">
<a
href="mailto:misha@mate.academy"
data-cy="CommentAuthor"
>
Misha Hrynko
</a>
const handleDeleteComment = (commentId: number) => {
setComments(prev => prev.filter(({ id }) => id !== commentId));
onDeleteComment(commentId);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find where you catch API errors, if you don't catch an error your app can crash.
Also, filter comments only if the comment was successfully deleted

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is written in the task to filter comments before delete

Implement comment deletion
Delete the commnet immediately not waiting for the server response to improve the UX.

Copy link

@BudnikOleksii BudnikOleksii Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is written in the task to filter comments before delete

Implement comment deletion
Delete the commnet immediately not waiting for the server response to improve the UX.

Sure, but you still need to handle possible API errors, and if actually comment wasn't added or deleted user should know about it. As it's weird if I add a comment, see it, then reload the page, and my comment disappears

Comment on lines 25 to 31
setIsLoading(true);
if (selectedPost) {
getComments(selectedPost.id)
.then(setComments)
.catch(() => setHasError(true))
.finally(() => setIsLoading(false));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
setIsLoading(true);
if (selectedPost) {
getComments(selectedPost.id)
.then(setComments)
.catch(() => setHasError(true))
.finally(() => setIsLoading(false));
}
if (selectedPost) {
setIsLoading(true);
getComments(selectedPost.id)
.then(setComments)
.catch(() => setHasError(true))
.finally(() => setIsLoading(false));
}

If selectedPost is null the isLoading state doesn't change to false.

But, as I see selectedPost is a required prop, so you can get rid of if (selectedPost) check

Comment on lines 39 to 43
{`#${selectedPost?.id}: ${selectedPost?.title}`}
</h2>

<p data-cy="PostBody">
{selectedPost?.body}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need these checks selectedPost?, in your Props type this prop is required

<tbody>
{posts.map((post) => {
return (
<tr data-cy="Post" key={post.id}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's create a Post component

src/App.tsx Outdated
Comment on lines 27 to 28
setSelectedUser(null);
setSelectedUser(newUser);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you set it to null, it doesn't work as expected in this case and makes no sense

src/App.tsx Outdated
<PostsList
selectedPost={selectedPost}
selectedUser={selectedUser}
onSelectPost={(post) => setSelectedPost(post)}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onSelectPost={(post) => setSelectedPost(post)}
onSelectPost={setSelectedPost}

Comment on lines 43 to 47
}

setCommentForm(prev => ({ ...prev, commentText: '' }));
setIsSubmitted(false);
setIsLoading(false);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be done in the finally block

Comment on lines 26 to 33
const toggleAddCommentForm = () => {
onAddComment(true);
};

<article className="message is-small" data-cy="Comment">
<div className="message-header">
<a
href="mailto:misha@mate.academy"
data-cy="CommentAuthor"
>
Misha Hrynko
</a>
const handleDeleteComment = (commentId: number) => {
setComments(prev => prev.filter(({ id }) => id !== commentId));
onDeleteComment(commentId);
};
Copy link

@BudnikOleksii BudnikOleksii Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is written in the task to filter comments before delete

Implement comment deletion
Delete the commnet immediately not waiting for the server response to improve the UX.

Sure, but you still need to handle possible API errors, and if actually comment wasn't added or deleted user should know about it. As it's weird if I add a comment, see it, then reload the page, and my comment disappears

Copy link

@BudnikOleksii BudnikOleksii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work 👍
But, you should handle API errors in any case.
Also, left a few comments above

Comment on lines 26 to 33
const toggleAddCommentForm = () => {
onAddComment(true);
};

const handleDeleteComment = (commentId: number) => {
setComments(prev => prev.filter(({ id }) => id !== commentId));
onDeleteComment(commentId);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't see where you handle API errors

Copy link

@BudnikOleksii BudnikOleksii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work 👍

Comment on lines +30 to +34
const handleDeleteComment = (commentId: number) => {
setComments(prev => prev.filter(({ id }) => id !== commentId));
try {
onDeleteComment(commentId);
} catch {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const handleDeleteComment = (commentId: number) => {
setComments(prev => prev.filter(({ id }) => id !== commentId));
try {
onDeleteComment(commentId);
} catch {
const handleDeleteComment = async (commentId: number) => {
setComments(prev => prev.filter(({ id }) => id !== commentId));
try {
await onDeleteComment(commentId);
} catch {

onDeleteComment is an async operation so you should wait for the result.

For this task it's okay, but in real life, if something goes wrong you should notify the user and get back previous data, for example, if a comment wasn't added successfully, you should remove it from the UI. The same if the deletion fails you should get back the comment and show it, and notify a user that the error occurred

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants